using System;
using System.Collections;
using System.Text;

namespace GAscheduler
{
    /// <summary>
    /// A schedule relating a gene (period/room) to each course, using a parallel array.
    /// </summary>
    class Schedule : IComparable<Schedule>, IEnumerable
    {
        public static double mutationRate = .01;//One in a hundred mutate randomly
        double preCalculatedFitness = double.MinValue;//Store fitness for future function calls
        Gene[] genes = new Gene[CourseManager.ALength];//Track all genes; Courses class has static member length

        /// <summary>
        /// Construct a schedule of random genes
        /// </summary>
        public Schedule()
        {
            for (int i = 0; i < CourseManager.ALength; i++)
                genes[i] = new Gene(i);
        }

        /// <summary>
        /// Breed two parents to construct this child, taking into account possible mutations
        /// </summary>
        /// <param name="parentA"></param>
        /// <param name="parentB"></param>
        public Schedule(Schedule parentA, Schedule parentB)
        {
            for (int i = 0; i < CourseManager.ALength; i++)
                //Either mutate each gene or copy it from a random parent using a coin toss
                if (new Random().NextDouble() < mutationRate)
                    genes[i] = new Gene(i);
                else
                    if (new Random().NextDouble() < .5)//coin flip
                        genes[i] = new Gene(parentA.get(i));
                    else
                        genes[i] = new Gene(parentB.get(i));
        }

        ///<summary>
        ///Mutate a parent to create a single child
        ///</summary>
        public Schedule(Schedule parent)
        {
            for (int i = 0; i < CourseManager.ALength; i++)
                if (new Random().NextDouble() < mutationRate)
                    genes[i] = new Gene(i);
                else
                    genes[i] = new Gene(parent.get(i));
        }

        /// <summary>
        /// Get gene at index into gene array
        /// </summary>
        /// <param name="i">gene index</param>
        /// <returns></returns>
        public Gene get(int i)
        {
            return genes[i];
        }

        public int getLength()
        {
            return genes.Length;
        }

        /// <summary>
        /// Calculate fitness, and save calculation for future calls
        /// </summary>
        /// <returns></returns>
        public double fitness()
        {
            int courses = CourseManager.ALength;
            int periods = 20;//PeriodManager.Length;

            if (preCalculatedFitness != double.MinValue)
                return preCalculatedFitness;


            /* Overview: Evenly distributed classes, throughout all
             *  periods, make schedules more fit
             * 
             * Technical: Create an array containing the number of
             * classes in each period, where each element of the array
             * represents a period, and contains the quantity of classes
             * within.
             * Treat the array as an n dimensional vector, where n is
             * the length/number of periods.
             * Calculate the length of the vector.
             * Calculate the dot product of the vector and a unit vector
             *    which is essentially the sum of the components,
             *    which is the same as the number of courses
             * Calculate the Dot Product / Length 
             * This number is always greater than 1, but less than
             *   the square root of n (quantity of periods)
             * We then normalize this number to fall within 0 and 1
             *
             * Thanks go to Lukas Dauterman for the ideas behind this algorithm
             */

            int[] scheduleDistribution = new int[periods];
            foreach (Gene gene in this)
                scheduleDistribution[gene.period]++;

            double squaredsum = 0;
            for (int i = 0; i < periods; i++)
                squaredsum += scheduleDistribution[i] ^ 2;

            double vecLength = Math.Sqrt(squaredsum);

            double ideal = Math.Sqrt(periods);
            double actual = courses / vecLength;
            //courses (length of courses array) is the same as the dot product

            preCalculatedFitness = (actual - 1) / (ideal - 1);

            return preCalculatedFitness;
            //TODO: Fitness calculation, eventually c++, but prototype in c# for sure
        }

        /// <summary>
        /// Determine if this schedule is more or less fit than another
        /// </summary>
        /// <param name="schedule"></param>
        /// <returns></returns>
        public int CompareTo(Schedule schedule)
        {
            if (this.fitness() > schedule.fitness())
                return 1;
            if (this.fitness() < schedule.fitness())
                return -1;
            else
                return 0;
        }

        public IEnumerator GetEnumerator()
        {
            foreach (Gene gene in genes)
            {
                yield return gene;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Return fitness decimal number as string representation
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return fitness().ToString();
        }
    }
}